#ifndef XRPL_PROTOCOL_SERIALIZER_H_INCLUDED
#define XRPL_PROTOCOL_SERIALIZER_H_INCLUDED

#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/SField.h>

#include <cstdint>
#include <cstring>
#include <type_traits>

namespace ripple {

class Serializer
{
private:
    // DEPRECATED
    Blob mData;

public:
    explicit Serializer(int n = 256)
    {
        mData.reserve(n);
    }

    Serializer(void const* data, std::size_t size)
    {
        mData.resize(size);

        if (size)
        {
            XRPL_ASSERT(
                data,
                "ripple::Serializer::Serializer(void const*) : non-null input");
            std::memcpy(mData.data(), data, size);
        }
    }

    Slice
    slice() const noexcept
    {
        return Slice(mData.data(), mData.size());
    }

    std::size_t
    size() const noexcept
    {
        return mData.size();
    }

    void const*
    data() const noexcept
    {
        return mData.data();
    }

    // assemble functions
    int
    add8(unsigned char i);
    int
    add16(std::uint16_t i);

    template <typename T>
        requires(std::is_same_v<
                 std::make_unsigned_t<std::remove_cv_t<T>>,
                 std::uint32_t>)
    int
    add32(T i)
    {
        int ret = mData.size();
        mData.push_back(static_cast<unsigned char>((i >> 24) & 0xff));
        mData.push_back(static_cast<unsigned char>((i >> 16) & 0xff));
        mData.push_back(static_cast<unsigned char>((i >> 8) & 0xff));
        mData.push_back(static_cast<unsigned char>(i & 0xff));
        return ret;
    }

    int
    add32(HashPrefix p);

    template <typename T>
        requires(std::is_same_v<
                 std::make_unsigned_t<std::remove_cv_t<T>>,
                 std::uint64_t>)
    int
    add64(T i)
    {
        int ret = mData.size();
        mData.push_back(static_cast<unsigned char>((i >> 56) & 0xff));
        mData.push_back(static_cast<unsigned char>((i >> 48) & 0xff));
        mData.push_back(static_cast<unsigned char>((i >> 40) & 0xff));
        mData.push_back(static_cast<unsigned char>((i >> 32) & 0xff));
        mData.push_back(static_cast<unsigned char>((i >> 24) & 0xff));
        mData.push_back(static_cast<unsigned char>((i >> 16) & 0xff));
        mData.push_back(static_cast<unsigned char>((i >> 8) & 0xff));
        mData.push_back(static_cast<unsigned char>(i & 0xff));
        return ret;
    }

    template <typename Integer>
    int addInteger(Integer);

    template <std::size_t Bits, class Tag>
    int
    addBitString(base_uint<Bits, Tag> const& v)
    {
        return addRaw(v.data(), v.size());
    }

    int
    addRaw(Blob const& vector);
    int
    addRaw(Slice slice);
    int
    addRaw(void const* ptr, int len);
    int
    addRaw(Serializer const& s);

    int
    addVL(Blob const& vector);
    int
    addVL(Slice const& slice);
    template <class Iter>
    int
    addVL(Iter begin, Iter end, int len);
    int
    addVL(void const* ptr, int len);

    // disassemble functions
    bool
    get8(int&, int offset) const;

    template <typename Integer>
    bool
    getInteger(Integer& number, int offset)
    {
        static auto const bytes = sizeof(Integer);
        if ((offset + bytes) > mData.size())
            return false;
        number = 0;

        auto ptr = &mData[offset];
        for (auto i = 0; i < bytes; ++i)
        {
            if (i)
                number <<= 8;
            number |= *ptr++;
        }
        return true;
    }

    template <std::size_t Bits, typename Tag = void>
    bool
    getBitString(base_uint<Bits, Tag>& data, int offset) const
    {
        auto success = (offset + (Bits / 8)) <= mData.size();
        if (success)
            memcpy(data.begin(), &(mData.front()) + offset, (Bits / 8));
        return success;
    }

    int
    addFieldID(int type, int name);
    int
    addFieldID(SerializedTypeID type, int name)
    {
        return addFieldID(safe_cast<int>(type), name);
    }

    // DEPRECATED
    uint256
    getSHA512Half() const;

    // totality functions
    Blob const&
    peekData() const
    {
        return mData;
    }
    Blob
    getData() const
    {
        return mData;
    }
    Blob&
    modData()
    {
        return mData;
    }

    int
    getDataLength() const
    {
        return mData.size();
    }
    void const*
    getDataPtr() const
    {
        return mData.data();
    }
    void*
    getDataPtr()
    {
        return mData.data();
    }
    int
    getLength() const
    {
        return mData.size();
    }
    std::string
    getString() const
    {
        return std::string(static_cast<char const*>(getDataPtr()), size());
    }
    void
    erase()
    {
        mData.clear();
    }
    bool
    chop(int num);

    // vector-like functions
    Blob ::iterator
    begin()
    {
        return mData.begin();
    }
    Blob ::iterator
    end()
    {
        return mData.end();
    }
    Blob ::const_iterator
    begin() const
    {
        return mData.begin();
    }
    Blob ::const_iterator
    end() const
    {
        return mData.end();
    }
    void
    reserve(size_t n)
    {
        mData.reserve(n);
    }
    void
    resize(size_t n)
    {
        mData.resize(n);
    }
    size_t
    capacity() const
    {
        return mData.capacity();
    }

    bool
    operator==(Blob const& v) const
    {
        return v == mData;
    }
    bool
    operator!=(Blob const& v) const
    {
        return v != mData;
    }
    bool
    operator==(Serializer const& v) const
    {
        return v.mData == mData;
    }
    bool
    operator!=(Serializer const& v) const
    {
        return v.mData != mData;
    }

    static int
    decodeLengthLength(int b1);
    static int
    decodeVLLength(int b1);
    static int
    decodeVLLength(int b1, int b2);
    static int
    decodeVLLength(int b1, int b2, int b3);

private:
    static int
    encodeLengthLength(int length);  // length to encode length
    int
    addEncoded(int length);
};

template <class Iter>
int
Serializer::addVL(Iter begin, Iter end, int len)
{
    int ret = addEncoded(len);
    for (; begin != end; ++begin)
    {
        addRaw(begin->data(), begin->size());
#ifndef NDEBUG
        len -= begin->size();
#endif
    }
    XRPL_ASSERT(
        len == 0, "ripple::Serializer::addVL : length matches distance");
    return ret;
}

//------------------------------------------------------------------------------

// DEPRECATED
// Transitional adapter to new serialization interfaces
class SerialIter
{
private:
    std::uint8_t const* p_;
    std::size_t remain_;
    std::size_t used_ = 0;

public:
    SerialIter(void const* data, std::size_t size) noexcept;

    SerialIter(Slice const& slice) : SerialIter(slice.data(), slice.size())
    {
    }

    // Infer the size of the data based on the size of the passed array.
    template <int N>
    explicit SerialIter(std::uint8_t const (&data)[N]) : SerialIter(&data[0], N)
    {
        static_assert(N > 0, "");
    }

    std::size_t
    empty() const noexcept
    {
        return remain_ == 0;
    }

    void
    reset() noexcept;

    int
    getBytesLeft() const noexcept
    {
        return static_cast<int>(remain_);
    }

    // get functions throw on error
    unsigned char
    get8();

    std::uint16_t
    get16();

    std::uint32_t
    get32();
    std::int32_t
    geti32();

    std::uint64_t
    get64();
    std::int64_t
    geti64();

    template <std::size_t Bits, class Tag = void>
    base_uint<Bits, Tag>
    getBitString();

    uint128
    get128()
    {
        return getBitString<128>();
    }

    uint160
    get160()
    {
        return getBitString<160>();
    }

    uint192
    get192()
    {
        return getBitString<192>();
    }

    uint256
    get256()
    {
        return getBitString<256>();
    }

    void
    getFieldID(int& type, int& name);

    // Returns the size of the VL if the
    // next object is a VL. Advances the iterator
    // to the beginning of the VL.
    int
    getVLDataLength();

    Slice
    getSlice(std::size_t bytes);

    // VFALCO DEPRECATED Returns a copy
    Blob
    getRaw(int size);

    // VFALCO DEPRECATED Returns a copy
    Blob
    getVL();

    void
    skip(int num);

    Buffer
    getVLBuffer();

    template <class T>
    T
    getRawHelper(int size);
};

template <std::size_t Bits, class Tag>
base_uint<Bits, Tag>
SerialIter::getBitString()
{
    auto const n = Bits / 8;

    if (remain_ < n)
        Throw<std::runtime_error>("invalid SerialIter getBitString");

    auto const x = p_;

    p_ += n;
    used_ += n;
    remain_ -= n;

    return base_uint<Bits, Tag>::fromVoid(x);
}

}  // namespace ripple

#endif
